// epoll、select、poll的区别：
// 1）相比于select和poll，epoll最大的好处在于不会随着监听fd数目的增加而降低效率
// 2）内核中的select与poll的实现是采用轮询来处理的，轮询数目越多耗时就越多
// 3）epoll的实现是基于回调的，如果fd有期望的事件发生就会通过回调函数将其加入
// epoll就绪队列中。也就是说它只关心“活跃”的fd，与fd数目无关。
// 4）内核空间用户空间数据拷贝问题，如何让内核把fd消息通知给用户空间？
// select和poll采取了内存拷贝的方法，而epoll采用的是共享内存的方式。速度快
// 5）epoll不仅会告诉应用程序有I/o事件的到来，还会告诉应用程序相关的信息，
// 这些信息是应用程序填充的，因此根据这写信息应用程序就能直接定位到事件，而不必遍历整个fd集合。
// 6）poll和select受进程能打开的最大文件描述符的限制。select还受FD_SETSIZE的限制。
// 但是epoll的限制的最大可以打开文件的数目（cat /proc/sys/fs/file-max进行查看）。

// epoll的工作模式
// 有下面两种工作模式，默认是水平触发。
// EPOLLLT：水平触发（Level Triggered），事件没有处理完也会触发。完全靠kernel epoll驱动，
// 应用程序只需要处理从epoll_wait返回的fds。这些fds我们认为他们处于就绪状态。
// LT模式支持阻塞fd和非阻塞fd。


// EPOLLET：边沿触发（Edge Triggered）。只有空闲->就绪才会触发。应用程序需要维护一个就绪队列。
// 此模式下，系统仅仅通知应用程序哪些fds变成了就绪状态，一旦fd变成了就绪状态，
// epoll将不再关注这个fd的任何状态信息（从epoll队列移除）。
// 直到应用程序通过读写操作触发EAGAIN状态，epoll认为这个fd又变成空闲状态，
// 那么epoll又重新关注这个fd的状态变化（重新加入epoll队列中）。
// 随着epoll_wait的返回，队列中的fds是减少的，所以在大并发的系统中，EPOLLET更有优势。但是对程序员的要求也更高。
// ET模式只支持non-block socket，以避免由于一个文件句柄的阻塞读/阻塞写把处理多个文件描述符的任务饿死。


#include<sys/types.h>
#include<sys/socket.h>
#include<sys/select.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<poll.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include <fcntl.h>
#include<sys/epoll.h>
#include<vector>
#include<algorithm>
// #include"comm.h"
void setfdisblock(int fd, bool isblock)
{
    int flags = fcntl(fd, F_GETFL);
    if(flags < 0)
        return;
    if(isblock) // 阻塞
    {
        flags &= ~O_NONBLOCK;
    }
    else // 非阻塞
    {
        flags |= O_NONBLOCK;
    }    
    
    if(fcntl(fd, F_SETFL, flags)<0)
        perror("fcntl set");
}
typedef std::vector<struct epoll_event> EventList;
#define CLIENTCOUNT 2048
#define MAX_EVENTS 2048
int main(int argc, char **argv)
{
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if(listenfd < 0)
    {
        perror("socket");
        return -1;
    }
    
    unsigned short sport = 9999;
    if(argc == 2)
    {
        sport = atoi(argv[1]);
    }
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    printf("port = %d\n", sport);
    addr.sin_port = htons(sport);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    if(bind(listenfd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
    {
        perror("bind");
        return -2;
    }
    if(listen(listenfd, 20) < 0)
    {
        perror("listen");
        return -3;
    }
        
    struct sockaddr_in connaddr;
    socklen_t len = sizeof(connaddr);
    
    int i = 0, ret = 0;
    std::vector<int> clients; // 客户端存储的迭代器
    int epollfd = epoll_create1(EPOLL_CLOEXEC);
    //int epollfd = epoll_create(MAX_EVENTS);// 设置连接数
    
    struct epoll_event event;
    event.events = EPOLLIN|EPOLLET;
    event.data.fd = listenfd;
    if(epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &event) < 0)
    {
        perror("epoll_ctl");
        return -2;
    }
    EventList events(16);
    int count = 0;
    int nready = 0;
    char buf[1024] = {0};
    int conn  = 0;
    while(1)
    {
        
        nready = epoll_wait(epollfd, &*events.begin(), static_cast<int>(events.size()), -1);
        if(nready == -1)
        {
            perror("epoll_wait");
                        return -3;
        }
        if(nready == 0) // 肯定不会走到这里，因为上面没设置超时时间
        {
            continue;
        }
        if((size_t)nready == events.size()) // 对clients进行扩容
            events.resize(events.size() * 2);
        for(i = 0; i < nready; i++)
        {
            if(events[i].data.fd == listenfd)
            {
                conn = accept(listenfd, (struct sockaddr*)&connaddr, &len);
                if(conn < 0)
                {
                    perror("accept");
                    return -4;
                }
                char strip[64] = {0};
                char *ip = inet_ntoa(connaddr.sin_addr);
                strcpy(strip, ip);
                printf("client connect, conn:%d,ip:%s, port:%d, count:%d\n", conn, strip,ntohs(connaddr.sin_port), ++count);
        
                clients.push_back(conn);
                // 设为非阻塞
                setfdisblock(conn, false);
                // add fd in events
                event.data.fd = conn;// 这样在epoll_wait返回时就可以直接用了
                event.events = EPOLLIN|EPOLLET;
                epoll_ctl(epollfd, EPOLL_CTL_ADD, conn, &event);
                
            }
            else if(events[i].events & EPOLLIN)
            {
                conn = events[i].data.fd;
                if(conn < 0)
                    continue;
                ret = read(conn, buf, sizeof(buf));
                if(ret == -1)
                {
                    perror("read");
                    return -5;
                }
                else if(ret == 0)
                {
                    printf("client close remove:%d, count:%d\n", conn, --count);
                    close(conn);
                    event = events[i];
                    epoll_ctl(epollfd, EPOLL_CTL_DEL, conn, &event);
                    clients.erase(std::remove(clients.begin(), clients.end(), conn), clients.end());
                }
                write(conn, buf, sizeof(buf));
                memset(buf, 0, sizeof(buf));
            }
        }    
    }
    close(listenfd);
    return 0;
}